package birdsong.spamFilter;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import birdsong.comm.ServerComm;
import birdsong.comm.ServerSideMessage;

/**
 * @author Grupo 48
 * 
 */
public class MessageFilter implements ServerComm {

	private ServerComm decoratedServerComm;
	private String[] sensitiveWords;
	HashMap<String, HashMap<Long, ServerSideMessage>> messageList = new HashMap<String, HashMap<Long, ServerSideMessage>>();
	HashMap<String, Long> blockList = new HashMap<String, Long>();

	/**
	 * Create the message filter.
	 * 
	 * @param decoratedServerComm
	 *            the server comm interface to decorate
	 * @param sensitiveWords
	 *            the words which the filter should check messages for. If a
	 *            Birdsong message from a client matches any of the words in the
	 *            array, it is rejected.
	 */

	public MessageFilter(ServerComm decoratedServerComm, String[] sensitiveWords) {
		this.decoratedServerComm = decoratedServerComm;
		this.sensitiveWords = sensitiveWords;

	}

	/**
	 * @see birdsong.comm.ServerComm#start()
	 */
	public void start() {
		decoratedServerComm.start();
	}

	/**
	 * The getNextMessage is the most important method of the Message Filter.
	 * The Message filter gets messages from the decoratedServerComm and decides
	 * what to do. Only not null messages from unblocked users and without the
	 * "censura" criteria are allowed by the filter to post. All the messages
	 * posted in the server are tracked by the filter (user and time) in order
	 * to search for repeated messages. Everytime the user post one message, the
	 * filter compares with the previous messages of the last 30 seconds of the
	 * user. 3 equal messages or 10 different messages in 30 seconds of interval
	 * = blocked user
	 */
	public ServerSideMessage getNextMessage() {

		boolean allow;
		ServerSideMessage message;

		do {
			allow = true;
			message = decoratedServerComm.getNextMessage();

			if (message != null && !isBlock(message.getClientNickname())) {
				if (!messageList.containsKey(message.getClientNickname())) {
					HashMap<Long, ServerSideMessage> msg = new HashMap<Long, ServerSideMessage>();
					msg.put(new Date().getTime(), message);
					messageList.put(message.getClientNickname(), msg);
				} else {
					messageList.get(message.getClientNickname()).put(
							new Date().getTime(), message);
				}

				if (message.getType() == ServerSideMessage.Type.BIRDSONG_MESSAGE) {
					int index = 0;
					System.out.println("Checking message: "
							+ message.getPayload());
					while (index < sensitiveWords.length && allow) {
						allow = !message.getPayload().contains(
								sensitiveWords[index++]);
						System.out.println("Checking message: "
								+ message.getPayload() + " againts '"
								+ sensitiveWords[index - 1] + "'");

					}
					if (!allow) {
						System.out.println("Message filtered");
						doBlock(message.getClientNickname());
						decoratedServerComm.disconnectClient(message
								.getClientNickname());
					}

				}
			}
			if (message != null) {
				if (isSend3Messages30Sec(message.getClientNickname(), message,
						new Date())
						|| isSend10Messages30Sec(message.getClientNickname(),
								message, new Date())) {
					allow = false;
				}
			}
		} while (!allow);

		return message;
	}

	/**
	 * @see birdsong.comm.ServerComm#hasNextMessage()
	 */
	@Override
	public boolean hasNextMessage() {
		return decoratedServerComm.hasNextMessage();
	}

	/**
	 * When a client has been disconnected by the spam filter, the client
	 * (identified by nickname) should not be allowed to connect again for a
	 * period of 30 seconds.
	 * 
	 * Method to verify if the user is blocked or was blocked in the last 30
	 * seconds. Also if the 30 seconds of block had passed, the user will be
	 * removed from the list of blocked users
	 */

	private boolean isBlock(String nickname) {
		if (!blockList.containsKey(nickname)) {
			return false;
		} else {
			Calendar cal = Calendar.getInstance();
			cal.setTime(new Date());
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();

			if (blockList.get(nickname) > timeInMillis) {
				return true;
			} else {
				blockList.remove(nickname);
				return false;
			}

		}
	}

	/**
	 * Add the user to the list of the blocked uers with the current time
	 */
	private void doBlock(String nickname) {
		blockList.put(nickname, new Date().getTime());
	}

	/**
	 * @see birdsong.comm.ServerComm#sendBirdsongMessage(java.lang.String,
	 *      java.lang.String, java.lang.String)
	 */
	public void sendBirdsongMessage(String fromNicename, String toNickname,
			String birdsongMessage) {
		decoratedServerComm.sendBirdsongMessage(fromNicename, toNickname,
				birdsongMessage);
	}

	/**
	 * @see birdsong.comm.ServerComm#sendNewFollower(java.lang.String,
	 *      java.lang.String)
	 */
	public void sendNewFollower(String toNickname, String newFollower) {
		decoratedServerComm.sendNewFollower(toNickname, newFollower);
	}

	/**
	 * @see birdsong.comm.ServerComm#sendNewUnfollow(java.lang.String,
	 *      java.lang.String)
	 */
	public void sendNewUnfollow(String toNickname, String unfollower) {
		decoratedServerComm.sendNewUnfollow(toNickname, unfollower);

	}

	/**
	 * @see birdsong.comm.ServerComm#sendError(java.lang.String,
	 *      java.lang.String)
	 */
	public void sendError(String toNickname, String error) {
		decoratedServerComm.sendError(toNickname, error);
	}

	/**
	 * @see birdsong.comm.ServerComm#sendClientConnected(java.lang.String,
	 *      java.lang.String)
	 */
	public void sendClientConnected(String toNickname, String connectedNickname) {
		decoratedServerComm.sendClientConnected(toNickname, connectedNickname);
	}

	/**
	 * @see birdsong.comm.ServerComm#sendClientDisconnected(java.lang.String,
	 *      java.lang.String)
	 */
	public void sendClientDisconnected(String toNickname,
			String disconnectedNickname) {
		decoratedServerComm.sendClientDisconnected(toNickname,
				disconnectedNickname);

	}

	/**
	 * @see birdsong.comm.ServerComm#clientIsConnected(java.lang.String)
	 */
	public boolean clientIsConnected(String nickname) {
		return decoratedServerComm.clientIsConnected(nickname);
	}

	/**
	 * @see birdsong.comm.ServerComm#disconnectClient(java.lang.String)
	 */
	public void disconnectClient(String nickname) {
		decoratedServerComm.disconnectClient(nickname);
	}

	/**
	 * 2. The spam filter should disconnect users who repeat the message more
	 * than three times within a span of 30 seconds. A warning should be sent
	 * when a user has repeated the same message twice within the time span.
	 * 
	 * The following method checks if the user had sent 3 times the same
	 * messages in less than 30 seconds in x phases: 1- Check if the user had
	 * already sent any message 2- If so, calculate the last 30 seconds of the
	 * current time 3- Iterate the list of messages and check if the time of the
	 * current message is greater or equal to the time of the last 30 seconds.
	 * 4- All the messages caught on step 3 will be counted and returned to the
	 * console when more than 2 are detected.
	 */

	private boolean isSend3Messages30Sec(String user,
			ServerSideMessage message, Date refDate) {
		if (!messageList.containsKey(user)) {
			return false;
		} else {
			HashMap<Long, ServerSideMessage> list = messageList.get(user);
			int numberMessages = 0;
			Calendar cal = Calendar.getInstance();
			cal.setTime(refDate);
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();

			Iterator it = list.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<Long, ServerSideMessage> a = (Map.Entry<Long, ServerSideMessage>) it
						.next();
				if (a.getKey() > timeInMillis
						&& compareObject(a.getValue(), message)) {
					numberMessages += 1;
				}
			}
			if (numberMessages >= 3) {
				System.out
						.println("More than three times within a span of 30 seconds");
				return true;
			}
		}
		return false;
	}

	/**
	 * The spam filter should disconnect users who send more than 10 messages
	 * within a span of 30 seconds. A warning should be sent when a user has
	 * sent more than 8 messages the time span.
	 * 
	 * The following method checks if the user had sent 10 messages in less than
	 * 30 seconds in x phases: 1- Check if the user had already sent any message
	 * 2- If so, calculate the last 30 seconds of the current time 3- Iterate
	 * the list of messages and check if the time of the current message is
	 * greater or equal to the time of the last 30 seconds. 4- All the messages
	 * caught on step 3 will be counted and returned to the console when more
	 * than 9 are detected.
	 */

	private boolean isSend10Messages30Sec(String user,
			ServerSideMessage message, Date refDate) {
		if (!messageList.containsKey(user)) {
			return false;
		} else {
			HashMap<Long, ServerSideMessage> list = messageList.get(user);
			int numberMessages = 0;
			Calendar cal = Calendar.getInstance();
			cal.setTime(refDate);
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();

			Iterator it = list.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<Long, ServerSideMessage> a = (Map.Entry<Long, ServerSideMessage>) it
						.next();
				if (a.getKey() > timeInMillis) {
					numberMessages += 1;
				}
			}

			if (numberMessages >= 10) {
				System.out
						.println("more than 10 messages within a span of 30 seconds");
				return true;
			}
		}
		return false;
	}

	/**
	 * Auxiliar method to compare messages. Check's if they are null, payload
	 * and type of message.
	 */

	// Aux method
	private boolean compareObject(ServerSideMessage a, ServerSideMessage b) {
		if (a == null && b == null) {
			return true;
		} else if (a != null && b == null || a == null && b != null) {
			return false;
		} else {

			return a.getPayload().equals(b.getPayload())
					&& a.getType().equals(b.getType());
		}
	}
}